// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Duan Lian (duanlian01@baidu.com)
//
// Description: OTA Reporter

#include "baidu_iot_ota_reporter.h"
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include "duer_log.h"
#include "baidu_json.h"
#include "heap_monitor.h"
#include "baidu_iot_ota_log.h"
#include "baidu_ca_scheduler.h"
#include "baidu_iot_ota_manager.h"

namespace duer {

static const char* const OTA_PROTOCOL_VERSION = "0.9";

OTAReporter::OTAReporter(const char* transaction_id) : _m_current_event(0), _m_lock() {
    if (transaction_id) {
        int transaction_id_len = strlen(transaction_id) + 1;
        _m_p_sztransaction_id = (char*)MALLOC(transaction_id_len, OTA);
        memmove(_m_p_sztransaction_id, transaction_id, transaction_id_len);
    }
}

OTAReporter::~OTAReporter() {
    if (_m_p_sztransaction_id) {
        FREE(_m_p_sztransaction_id);
        _m_p_sztransaction_id = NULL;
    }
}

int OTAReporter::report_state(int state, ...) {
    char* str_payload = NULL;
    baidu_json* json_payload = NULL;
    json_payload = baidu_json_CreateObject();

    if (json_payload) {
        baidu_json* json_state = NULL;
        json_state = baidu_json_CreateObject();

        if (json_state) {
            baidu_json_AddStringToObject(json_state, "transaction", _m_p_sztransaction_id);
            baidu_json_AddNumberToObject(json_state, "state", state);

            if (OTA_STATE_DOWNLOADING == state) {
                double db_percent = 0;
                va_list args;
                va_start(args, state);
                db_percent = va_arg(args, double);
                va_end(args);
                baidu_json_AddNumberToObject(json_state, "percent", db_percent);
            }

            baidu_json_AddItemToObject(json_payload, "ota_state", json_state);
            str_payload = baidu_json_PrintUnformatted(json_payload);
        }

        baidu_json_Delete(json_payload);
    }

    if (str_payload) {
        baidu_json_release(str_payload);
    }

    return 0;
}

int OTAReporter::report_event(int event, ...) {
    _m_lock.lock();

    // Only report error once
    if (_m_current_event == OTA_EVENT_CONNECT_FAIL
            || _m_current_event == OTA_EVENT_CONNECTION_LOST
            || _m_current_event == OTA_EVENT_DOWNLOAD_FAIL
            || _m_current_event == OTA_EVENT_IMAGE_INVALID
            || _m_current_event == OTA_EVENT_WRITE_ERROR
            || _m_current_event == OTA_EVENT_REJECT) {
        return -1;
    } else {
        _m_current_event = event;
    }

    _m_lock.unlock();
    char* str_payload = NULL;
    baidu_json* json_data = NULL;
    json_data = baidu_json_CreateObject();

    if (!json_data) {
        return 1;
    }

    baidu_json* json_datapoint = NULL;
    json_datapoint = baidu_json_CreateObject();

    if (json_datapoint) {
        baidu_json_AddItemToObject(json_data, "data", json_datapoint);
        baidu_json* json_payload = NULL;
        json_payload = baidu_json_CreateObject();

        if (json_payload) {
            baidu_json_AddItemToObject(json_datapoint, "ota_event", json_payload);
            baidu_json_AddStringToObject(json_payload, "transaction", _m_p_sztransaction_id);
            baidu_json_AddNumberToObject(json_payload, "event", event);

            if (OTAReporter::OTA_EVENT_DOWNLOADING == event) {
                double db_percent = 0.0;
                va_list args;
                va_start(args, event);
                db_percent = va_arg(args, double);
                va_end(args);
                baidu_json_AddNumberToObject(json_payload, "percent", db_percent);
            }

            str_payload = baidu_json_PrintUnformatted(json_data);
        }
    }

    baidu_json_Delete(json_data);

    if (str_payload) {
        DUER_LOGD("%s", str_payload);
        Scheduler::instance().report(str_payload, strlen(str_payload));
        baidu_json_release(str_payload);
    }

    return 0;
}

int OTAReporter::report_package_info(struct os_info& os) {
    char* str_payload = NULL;
    baidu_json* json_data = NULL;
    baidu_json* json_data_point = NULL;
    json_data = baidu_json_CreateObject();

    OTA_FUNC_ENTER();

    if (!json_data) {
        return 0;
    }

    json_data_point = baidu_json_CreateObject();

    if (json_data_point) {
        baidu_json_AddItemToObject(json_data, "data", json_data_point);
        baidu_json* json_payload = NULL;
        json_payload = baidu_json_CreateObject();

        if (json_payload) {
            baidu_json* json_os = NULL;
            baidu_json_AddItemToObject(json_data_point, "package_info", json_payload);
            json_os = baidu_json_CreateObject();

            if (json_os) {
                baidu_json_AddStringToObject(json_payload, "version", OTA_PROTOCOL_VERSION);
                baidu_json_AddStringToObject(json_payload, "product", "stereo");
                baidu_json_AddStringToObject(json_payload, "batch", "12");
                baidu_json_AddItemToObject(json_payload, "os", json_os);
                baidu_json_AddStringToObject(json_os, "name", os.name);
                baidu_json_AddStringToObject(json_os, "developer", os.developer);
                baidu_json_AddStringToObject(json_os, "version", os.version);
                baidu_json_AddStringToObject(json_os, "staged_version", os.staged_version);
                str_payload = baidu_json_PrintUnformatted(json_data);
            }
        }
    }

    baidu_json_Delete(json_data);

    if (str_payload) {
        DUER_LOGD("%s", str_payload);
        Scheduler::instance().report(str_payload, strlen(str_payload));
        baidu_json_release(str_payload);
    }

    OTA_FUNC_LEAVE();

    return 0;
}

void OTAReporter::ota_report_status(Object& values) {
    Object package_info;
    get_packager_info(package_info);
    values.receiveObject("package_info", package_info.releaseObject());
}

void OTAReporter::get_packager_info(Object& obj) {
    obj.putString("version", OTA_PROTOCOL_VERSION);
    obj.putString("product", "stereo");
    obj.putString("batch", "12");
    Object os;

    struct os_info os_info;
    int ret = OTAManager::instance()->get_os_info(os_info);
    if (ret == 0) {
        os.putString("name", os_info.name);
        os.putString("developer", os_info.developer);
        os.putString("version", os_info.version);
        os.putString("staged_version", os_info.staged_version);
    }

    obj.receiveObject("os", os.releaseObject());
}

} // namespace duer
